﻿using Autofac;
using Microsoft.EntityFrameworkCore;
using System.Threading;
using System.Threading.Tasks;
using Loong.Domain.Uow;
using Loong.Extensions;
using Loong.Transactions.Extensions;

namespace Loong.EntityFrameworkCore.Uow
{
    public class EfCoreUnitOfWork : UnitOfWorkBase
    {
        private readonly SemaphoreSlim _locker;
        private readonly ILifetimeScope _lifetimeScope;

        protected DbContext ActiveDbContext { get; private set; }

        public EfCoreUnitOfWork(
            IUnitOfWorkDefaultOptions defaultOptions,
            IUnitOfWorkFilterExecuter filterExecuter,
            ILifetimeScope lifetimeScope)
            : base(defaultOptions, filterExecuter)
        {
            _locker = new SemaphoreSlim(1);
            _lifetimeScope = lifetimeScope;
        }

        public override void SaveChanges()
        {
            ActiveDbContext?.SaveChanges();
        }

        public override async Task SaveChangesAsync()
        {
            if (ActiveDbContext != null)
            {
                await ActiveDbContext.SaveChangesAsync();
            }
        }

        protected override void CompleteUow()
        {
            SaveChanges();
            CommitTransaction();
        }

        private void CommitTransaction()
        {
            if (Options.IsTransactional == true &&
                ActiveDbContext != null)
            {
                ActiveDbContext.Database.CurrentTransaction?.Commit();
            }
        }

        protected override async Task CompleteUowAsync()
        {
            await SaveChangesAsync();
            await CommitTransactionAsync();
        }

        private async Task CommitTransactionAsync()
        {
            if (Options.IsTransactional == true &&
                ActiveDbContext != null &&
                ActiveDbContext.Database.CurrentTransaction != null)
            {
                await ActiveDbContext.Database.CurrentTransaction.CommitAsync();
            }
        }

        protected override void DisposeUow()
        {
            if (ActiveDbContext != null)
            {
                ActiveDbContext.Database.CurrentTransaction?.Dispose();
                ActiveDbContext.Dispose();
                ActiveDbContext = null;
            }
        }

        public virtual TDbContext GetOrCreateDbContext<TDbContext>()
            where TDbContext : DbContext
        {
            try
            {
                _locker.Wait();

                if (ActiveDbContext == null)
                {
                    ActiveDbContext = _lifetimeScope.Resolve<TDbContext>();

                    if (Options.IsTransactional == true)
                    {
                        var isolationLevel = (Options.IsolationLevel ?? System.Transactions.IsolationLevel.ReadCommitted).ToSystemDataIsolationLevel();
                        ActiveDbContext.Database.BeginTransaction(isolationLevel);
                    }

                    if (Options.Timeout.HasValue &&
                        ActiveDbContext.Database.IsRelational() &&
                        !ActiveDbContext.Database.GetCommandTimeout().HasValue)
                    {
                        ActiveDbContext.Database.SetCommandTimeout(Options.Timeout.Value.TotalSeconds.To<int>());
                    }
                }

                return (TDbContext)ActiveDbContext;
            }
            finally
            {
                _locker.Release();
            }
        }
    }
}
